/*******************************************************************************
 * Copyright (c) 2000, 2003 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aseclipse.core.runtime.registry.classes {

import flash.utils.getDefinitionByName;

import mx.utils.StringUtil;

import org.aseclipse.core.runtime.registry.IConfigurationElement;
import org.aseclipse.core.runtime.registry.IExecutableExtension;
import org.aseclipse.core.runtime.registry.IExtension;
import org.aseclipse.osgi.Bundle;
import org.aseclipse.osgi.BundleEvent;

/**
 * An object which represents the user-defined contents of an extension
 * in a plug-in manifest.
 * <p>
 * This class may be instantiated, or further subclassed.
 * </p>
 */

public class ConfigurationElement extends RegistryModelObject implements IConfigurationElement {

	// DTD properties (included in plug-in manifest)
	private var value:String = null;
	private var properties:Array = null; // ConfigurationProperty[]
	private var children:Array = null;   // IConfigurationElement[]

	//This field is only used when doing createExecutableExtension 
	private var oldStyleConfigurationElement:Object = null;
	
    /**
	 * Creates and returns a new instance of the executable extension 
	 * identified by the named attribute of this configuration element.
	 * The named attribute value must contain a fully qualified name
	 * of an actionscript class implementing the executable extension.
	 * <p>
	 * The specified class is instantiated using its 0-argument public 
	 * constructor. If the specified class implements the
	 * <code>IExecutableExtension</code> interface, the method
	 * <code>setInitializationData</code> is called, passing to the object
	 * the configuration information that was used to create it. 
	 * </p>
	 * <p>
	 * Unlike other methods on this object, invoking this method may activate 
	 * the plug-in.
	 * </p>
	 *
	 * @param propertyName the name of the property which is as class. The format of value can be:
	 *    complex format is "pluginID/className:initData";
	 *    simple format is "className"; 
	 *  if wanting to use configuration element's value as class name, you can set propertyName as null
	 * @param receiver callback function when a new object is created, the function format is like "function(obj:Object):void"
	 * @see IExecutableExtension#setInitializationData
	 */
	public function createExecutableExtension(attributeName:String, receiver:Function):void {
		var prop:String = null;
		var executable:String;
		var pluginName:String = null;
		var className:String = null;
		var initData:Object = null;
		var i:int;

		if (attributeName != null)
			prop = getAttribute(attributeName);
		else {
			// property not specified, try as element value
			prop = getValue();
			if (prop != null) {
				prop = StringUtil.trim(prop);
				if (prop == "") //$NON-NLS-1$
					prop = null;
			}
		}

		if (prop == null) {
			// property not defined, try as a child element
			throw new Error("[ConfigurationElement.createExecutableExtension] no property defined for " + attributeName + " on element " + getName());
		} else {
			// simple property or element value, parse it into its components
			i = prop.indexOf(':');
			if (i != -1) {
				executable = StringUtil.trim(prop.substring(0, i));
				initData = StringUtil.trim(prop.substring(i + 1));
			} else
				executable = prop;

			i = executable.indexOf('/');
			if (i != -1) {
				pluginName = StringUtil.trim(executable.substring(0, i));
				className = StringUtil.trim(executable.substring(i + 1));
			} else
				className = executable;
		}

		if (className == null || className == "")  //$NON-NLS-1$
			throw new Error("no class defined for executable extension");
		// check plugin is started
		checkStarted(pluginName, className, initData, this, attributeName, receiver);
	}
    
    public function resolveItself(callback:Function):void {
        var model:BundleModel = getParentBundleModel();
        var pluginId:String = model.owner.getSymbolicName();
        var plugin:Bundle = model.framework.getBundle(pluginId);
        if(plugin != null && plugin.getState() == Bundle.RESOLVED) {
            trace("resolveItself '" + pluginId + "' successfully.");
            callback.apply(this, null);
        } else {
            model.framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, 
             function(event:BundleEvent):void {
    		    if(event.getState() == BundleEvent.RESOLVED && event.getBundle().getSymbolicName() == pluginId) {
    		        trace("resolveItself '" + pluginId + "' successfully.");
    		        callback.apply(this, null);
    		    }
    		});
    		model.framework.resolveBundle(pluginId);
        }
    }
    
    public function startItself(callback:Function):void {
        var model:BundleModel = getParentBundleModel();
        var pluginId:String = model.owner.getSymbolicName();
        var plugin:Bundle = model.framework.getBundle(pluginId);
        if(plugin != null && plugin.getState() == Bundle.RESOLVED) {
            trace("resolveItself '" + pluginId + "' successfully.");
            callback.apply(this, null);
        } else {
            model.framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, 
             function(event:BundleEvent):void {
    		    if(event.getState() == BundleEvent.STARTED && event.getBundle().getSymbolicName() == pluginId) {
    		        trace("resolveItself '" + pluginId + "' successfully.");
    		        callback.apply(this, null);
    		    }
    		});
    		model.framework.startBundle(pluginId);
        }
    }
    
    /**
	 * Installs and starts a new plugin specified by property value
	 * The named attribute value must contain a fully qualified name
	 * of an plugin.
	 *  
	 * <p>
	 * Unlike other methods on this object, invoking this method may activate 
	 * the current plug-in.
	 * </p>
	 * 
	 * @param propertyName the name of the property which is plugin id
	 * @param callback function callbacked when plugin is started
	 */ 
    public function startPlugin(propertyName:String, callback:Function):void {
        var model:BundleModel = getParentBundleModel();
        var pluginId:String = getAttribute(propertyName);
        var plugin:Bundle = model.framework.getBundle(pluginId);
        if(plugin != null && plugin.getState() == Bundle.ACTIVE) {
            trace("startPlugin '" + pluginId + "' successfully.");
            callback.apply(this, null);
        } else {
            model.framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, 
             function(event:BundleEvent):void {
    		    if(event.getState() == BundleEvent.INSTALLED && event.getBundle().getSymbolicName() == pluginId) {
    		        trace("startPlugin '" + pluginId + "' successfully.");
    		        callback.apply(this, null);
    		    }
    		});
    		model.framework.startBundle(pluginId);
        }
    }
    
	/**
	 * Returns the extension in which this configuration element is declared.
	 * If this element is a top-level child of an extension, the returned value
	 * is equivalent to <code>getParent</code>.
	 *
	 * @return the extension in which this configuration element is declared
	 *  or <code>null</code>
	 */
	public function getParentExtension():Extension {
		var p:Object = getParent();
		while (p != null && p is ConfigurationElement)
			p = (p as ConfigurationElement).getParent();
		return (p as Extension);
	}
	
	public function getDeclaringExtension():IExtension {
		return getParentExtension() as IExtension;
	}
	
	/****
	 * @private
	 * internal usage
	 */ 
	public function getParentBundleModel():BundleModel {
	    var p:Extension = getParentExtension();
	    return p.getParent() as BundleModel;
	}
	
	/**
	 * Returns the properties associated with this element.
	 *
	 * @return the properties associated with this element
	 *  or <code>null</code>
	 */
	public function getProperties():Array { // ConfigurationProperty[]
		return properties;
	}
	/**
	 * Returns the value of this element.
	 * 
	 * @return the value of this element or <code>null</code>
	 */
	public function getValue():String {
		return getValueAsIs();
		/* if (s == null)
			return null;
		bundleModel:BundleModel = (getDeclaringExtension() as Extension).getParent() as BundleModel;
		return bundleModel.getResourceString(s); */
	}
	public function getValueAsIs():String {
		return value;
	}

	/**
	 * Returns this element's sub-elements.
	 *
	 * @return the sub-elements of this element or <code>null</code>
	 */
	public function getChildren():Array { // IConfigurationElement[]
		return children == null ? new IConfigurationElement[0] : children;
	}
	public function getChildrenByName(name:String):Array { // IConfigurationElement[]
		var list:Array = getChildren();
		if (list == null)
			return new IConfigurationElement[0];
		var children:Array = new Array();
		for (var i:int = 0; i < list.length; i++) {
			var element:IConfigurationElement = list[i];
			if (name == element.getName())
				children.push(list[i]);
		}
		return children;
	}

	public function getAttribute(name:String):String {
		var list:Array = getProperties();
		if (list == null)
			return null;

		var found:ConfigurationProperty = null;
		for (var i:int = 0; i < list.length; i++)
			if (name == list[i].getName()) {
				found = list[i];
				break;
			}
		var s:String;
		if (found == null || (s = found.getValue()) == null)
			return null;
		return s;	
		//replace the key with its localized value
		//BundleModel bundleModel = (BundleModel) ((Extension) getDeclaringExtension()).getParent();
		//String localized = bundleModel.getResourceString(s);
		//if (localized != s)
		//	found.setLocalizedValue(localized);
		//return localized;
	}
	public function getAttributeAsIs(name:String):String {
		var list:Array = getProperties();
		if (list == null)
			return null;
		for (var i:int = 0; i < list.length; i++)
			if (name == (list[i] as ConfigurationProperty).getName())
				return (list[i] as ConfigurationProperty).getValue();
		return null;
	}
	public function getAttributeNames():Array {  // String[]
		var list:Array = getProperties();
		if (list == null)
			return new String[0];
		var result:Array = new Array(list.length);
		for (var i:int = 0; i < list.length; i++)
			result[i] = list[i].getName();
		return result;
	}
	/**
	 * Sets the properties associated with this element.  This object must not be read-only.
	 *
	 * @param value the properties to associate with this element.  May be <code>null</code>.
	 */
	public function setProperties(value:Array):void {  // ConfigurationProperty[]
		assertIsWriteable();
		properties = value;
	}
	/**
	 * Sets configuration elements contained by this element
	 * This object must not be read-only.
	 *
	 * @param value the configuration elements to be associated with this element.  
	 *		May be <code>null</code>.
	 */
	public function setChildren(value:Array):void { // IConfigurationElement[]
		assertIsWriteable();
		children = value;
	}
	/**
	 * Sets the value of this element.  This object must not be read-only.
	 * 
	 * @param value the new value of this element.  May be <code>null</code>.
	 */
	public function setValue(value:String):void {
		assertIsWriteable();
		this.value = value;
	}

	public function setOldStyleConfigurationElement(ce:Object):void {
		oldStyleConfigurationElement = ce;
	}


	private function checkStarted(pluginName:String, className:String, initData:Object, 
	     cfig:IConfigurationElement, propertyName:String, callback:Function):void {
	    var model:BundleModel = getParentBundleModel();
	    var targetPlugin:String = model.owner.getSymbolicName();
		if (pluginName != null && !pluginName == "" && pluginName != targetPlugin) { //$NON-NLS-1$
			targetPlugin = pluginName;
		}
		var bundle:Bundle = model.framework.getBundle(targetPlugin);
		if(bundle != null && bundle.getState() == Bundle.ACTIVE) {
		    initAndCallback(className, initData, cfig, propertyName, callback);
		} else {
		    model.framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, function(event:BundleEvent):void {
    		    if(event.getState() == BundleEvent.STARTED 
    		       && event.getBundle().getSymbolicName() == targetPlugin) {
    		        initAndCallback(className, initData, cfig, propertyName, callback);
    		    }
    		});
    		model.framework.startBundle(targetPlugin);
		}
     }

	private function initAndCallback(className:String, initData:Object, 
	     cfig:IConfigurationElement, propertyName:String, callback:Function):void {	
		// load the requested class from this plugin
		var classInstance:Class = null;
		try {
			classInstance = getDefinitionByName(className) as Class;
		} catch (e1:Error) {
			// throwException(Policy.bind("plugin.loadClassError", bundle.getGlobalName(), className), e1); //$NON-NLS-1$
			// throw new Error("Class '" + className + "' is not in current Flash Player" + e1.getStackTrace());
			throw e1;
		}

		// create a new instance
		var result:Object = null;
		try {
			result = new classInstance();
		} catch (e:Error) {
		    throw e; //new Error("plugin.instantiateClassError");
			// throwException(Policy.bind("plugin.instantiateClassError", bundle.getGlobalName(), className), e); //$NON-NLS-1$
		}

		// check if we have extension adapter and initialize
		if (result is IExecutableExtension) {
			try {
				// make the call even if the initialization string is null
				 (result as IExecutableExtension).setInitializationData(cfig, propertyName, initData);
			} catch (te:Error) {
				throw new Error("Fail to intialize " + className + " at method 'setInitializationData'");
			}
		}
        if(callback != null) {
            callback.apply(this, [result]);
        }
        trace("createExecutableExtension '" + className + "' successfully.");
	}
}
}